module.exports = Polygon

var util = require('util')

var Geometry = require('./geometry')
var Types = require('./types')
var Point = require('./point')
var BinaryWriter = require('./binarywriter')

function Polygon(exteriorRing, interiorRings, srid) {
  Geometry.call(this)

  this.exteriorRing = exteriorRing || []
  this.interiorRings = interiorRings || []
  this.srid = srid

  if (this.exteriorRing.length > 0) {
    this.hasZ = this.exteriorRing[0].hasZ
    this.hasM = this.exteriorRing[0].hasM
  }
}

util.inherits(Polygon, Geometry)

Polygon.Z = function(exteriorRing, interiorRings, srid) {
  var polygon = new Polygon(exteriorRing, interiorRings, srid)
  polygon.hasZ = true
  return polygon
}

Polygon.M = function(exteriorRing, interiorRings, srid) {
  var polygon = new Polygon(exteriorRing, interiorRings, srid)
  polygon.hasM = true
  return polygon
}

Polygon.ZM = function(exteriorRing, interiorRings, srid) {
  var polygon = new Polygon(exteriorRing, interiorRings, srid)
  polygon.hasZ = true
  polygon.hasM = true
  return polygon
}

Polygon._parseWkt = function(value, options) {
  var polygon = new Polygon()
  polygon.srid = options.srid
  polygon.hasZ = options.hasZ
  polygon.hasM = options.hasM

  if (value.isMatch(['EMPTY'])) { return polygon }

  value.expectGroupStart()

  value.expectGroupStart()
  polygon.exteriorRing.push.apply(polygon.exteriorRing, value.matchCoordinates(options))
  value.expectGroupEnd()

  while (value.isMatch([','])) {
    value.expectGroupStart()
    polygon.interiorRings.push(value.matchCoordinates(options))
    value.expectGroupEnd()
  }

  value.expectGroupEnd()

  return polygon
}

Polygon._parseWkb = function(value, options) {
  var polygon = new Polygon()
  polygon.srid = options.srid
  polygon.hasZ = options.hasZ
  polygon.hasM = options.hasM

  var ringCount = value.readUInt32()

  if (ringCount > 0) {
    var exteriorRingCount = value.readUInt32()

    for (var i = 0; i < exteriorRingCount; i++) { polygon.exteriorRing.push(Point._readWkbPoint(value, options)) }

    for (i = 1; i < ringCount; i++) {
      var interiorRing = []

      var interiorRingCount = value.readUInt32()

      for (var j = 0; j < interiorRingCount; j++) { interiorRing.push(Point._readWkbPoint(value, options)) }

      polygon.interiorRings.push(interiorRing)
    }
  }

  return polygon
}

Polygon._parseTwkb = function(value, options) {
  var polygon = new Polygon()
  polygon.hasZ = options.hasZ
  polygon.hasM = options.hasM

  if (options.isEmpty) { return polygon }

  var previousPoint = new Point(0, 0, options.hasZ ? 0 : undefined, options.hasM ? 0 : undefined)
  var ringCount = value.readVarInt()
  var exteriorRingCount = value.readVarInt()

  for (var i = 0; i < exteriorRingCount; i++) { polygon.exteriorRing.push(Point._readTwkbPoint(value, options, previousPoint)) }

  for (i = 1; i < ringCount; i++) {
    var interiorRing = []

    var interiorRingCount = value.readVarInt()

    for (var j = 0; j < interiorRingCount; j++) { interiorRing.push(Point._readTwkbPoint(value, options, previousPoint)) }

    polygon.interiorRings.push(interiorRing)
  }

  return polygon
}

Polygon._parseGeoJSON = function(value) {
  var polygon = new Polygon()

  if (value.coordinates.length > 0 && value.coordinates[0].length > 0) { polygon.hasZ = value.coordinates[0][0].length > 2 }

  for (var i = 0; i < value.coordinates.length; i++) {
    if (i > 0) { polygon.interiorRings.push([]) }

    for (var j = 0; j < value.coordinates[i].length; j++) {
      if (i === 0) { polygon.exteriorRing.push(Point._readGeoJSONPoint(value.coordinates[i][j])) } else { polygon.interiorRings[i - 1].push(Point._readGeoJSONPoint(value.coordinates[i][j])) }
    }
  }

  return polygon
}

Polygon.prototype.toWkt = function() {
  if (this.exteriorRing.length === 0) { return this._getWktType(Types.wkt.Polygon, true) }

  return this._getWktType(Types.wkt.Polygon, false) + this._toInnerWkt()
}

Polygon.prototype._toInnerWkt = function() {
  var innerWkt = '(('

  for (var i = 0; i < this.exteriorRing.length; i++) { innerWkt += this._getWktCoordinate(this.exteriorRing[i]) + ',' }

  innerWkt = innerWkt.slice(0, -1)
  innerWkt += ')'

  for (i = 0; i < this.interiorRings.length; i++) {
    innerWkt += ',('

    for (var j = 0; j < this.interiorRings[i].length; j++) {
      innerWkt += this._getWktCoordinate(this.interiorRings[i][j]) + ','
    }

    innerWkt = innerWkt.slice(0, -1)
    innerWkt += ')'
  }

  innerWkt += ')'

  return innerWkt
}

Polygon.prototype.toWkb = function(parentOptions) {
  var wkb = new BinaryWriter(this._getWkbSize())

  wkb.writeInt8(1)

  this._writeWkbType(wkb, Types.wkb.Polygon, parentOptions)

  if (this.exteriorRing.length > 0) {
    wkb.writeUInt32LE(1 + this.interiorRings.length)
    wkb.writeUInt32LE(this.exteriorRing.length)
  } else {
    wkb.writeUInt32LE(0)
  }

  for (var i = 0; i < this.exteriorRing.length; i++) { this.exteriorRing[i]._writeWkbPoint(wkb) }

  for (i = 0; i < this.interiorRings.length; i++) {
    wkb.writeUInt32LE(this.interiorRings[i].length)

    for (var j = 0; j < this.interiorRings[i].length; j++) { this.interiorRings[i][j]._writeWkbPoint(wkb) }
  }

  return wkb.buffer
}

Polygon.prototype.toTwkb = function() {
  var twkb = new BinaryWriter(0, true)

  var precision = Geometry.getTwkbPrecision(5, 0, 0)
  var isEmpty = this.exteriorRing.length === 0

  this._writeTwkbHeader(twkb, Types.wkb.Polygon, precision, isEmpty)

  if (this.exteriorRing.length > 0) {
    twkb.writeVarInt(1 + this.interiorRings.length)

    twkb.writeVarInt(this.exteriorRing.length)

    var previousPoint = new Point(0, 0, 0, 0)
    for (var i = 0; i < this.exteriorRing.length; i++) { this.exteriorRing[i]._writeTwkbPoint(twkb, precision, previousPoint) }

    for (i = 0; i < this.interiorRings.length; i++) {
      twkb.writeVarInt(this.interiorRings[i].length)

      for (var j = 0; j < this.interiorRings[i].length; j++) { this.interiorRings[i][j]._writeTwkbPoint(twkb, precision, previousPoint) }
    }
  }

  return twkb.buffer
}

Polygon.prototype._getWkbSize = function() {
  var coordinateSize = 16

  if (this.hasZ) { coordinateSize += 8 }
  if (this.hasM) { coordinateSize += 8 }

  var size = 1 + 4 + 4

  if (this.exteriorRing.length > 0) { size += 4 + (this.exteriorRing.length * coordinateSize) }

  for (var i = 0; i < this.interiorRings.length; i++) { size += 4 + (this.interiorRings[i].length * coordinateSize) }

  return size
}

Polygon.prototype.toGeoJSON = function(options) {
  var geoJSON = Geometry.prototype.toGeoJSON.call(this, options)
  geoJSON.type = Types.geoJSON.Polygon
  geoJSON.coordinates = []

  if (this.exteriorRing.length > 0) {
    var exteriorRing = []

    for (var i = 0; i < this.exteriorRing.length; i++) {
      if (this.hasZ) { exteriorRing.push([this.exteriorRing[i].x, this.exteriorRing[i].y, this.exteriorRing[i].z]) } else { exteriorRing.push([this.exteriorRing[i].x, this.exteriorRing[i].y]) }
    }

    geoJSON.coordinates.push(exteriorRing)
  }

  for (var j = 0; j < this.interiorRings.length; j++) {
    var interiorRing = []

    for (var k = 0; k < this.interiorRings[j].length; k++) {
      if (this.hasZ) { interiorRing.push([this.interiorRings[j][k].x, this.interiorRings[j][k].y, this.interiorRings[j][k].z]) } else { interiorRing.push([this.interiorRings[j][k].x, this.interiorRings[j][k].y]) }
    }

    geoJSON.coordinates.push(interiorRing)
  }

  return geoJSON
}
